/*
 *  MetaTest.h
 *  MetaThrift
 *
 *  Created by Ian Pye on 3/16/08.
 *  Copyright 2008 TriceratopsPowered. All rights reserved.
 *
 */

// basic BSD headers
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sstream>

// Thrift headers
#include <protocol/TBinaryProtocol.h>
#include <transport/TSocket.h>
#include <transport/TTransportUtils.h>

// Tut headers
#include <tut.h>
#include <tut_reporter.h>

// Loggers
#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>
#include <log4cxx/helpers/exception.h>
#include <log4cxx/propertyconfigurator.h>

// My Headers
#include "RingerMetaServer.h"
#include "Ringer.h"
#include "ringer_constants.h"
#include "RingerFS.h"
#include "FileUtil.h"
#include "NetworkUtil.h"
#include "MetaTest.h"

using namespace std;
using namespace facebook::thrift;
using namespace facebook::thrift::protocol;
using namespace facebook::thrift::transport;

using namespace ringer;
using namespace boost;

using namespace log4cxx;
using namespace log4cxx::helpers;

const int NUM_TIMES_TO_TIME = 10;

namespace tut{
  test_runner_singleton runner;
  
  struct client // (1)
	{ 
    std::string test_path;
    std::string test_content;
    std::string test_tag;
    
    client(){ 
      test_path = "/This/is/the/test/path";
      test_content = "THIS IS THE TEST CONTENT!!!!";
      test_tag = "tag1";
    }
    ~client(){ 
    }
	};
  
  typedef	test_group<client> tg;
  tg client("MDS tests");
  
  /**
   * Checks ping
   */
  template<>
  template<>
  void tg::object::test<1>()
  {
#ifdef __APPLE__
    set_test_name("ping test");
#endif    
    ensure_equals(true, ringer_client_ping());
  }
  
  /**
   * Checks add
   */
  template<>
  template<>
  void tg::object::test<2>()
  {
#ifdef __APPLE__  
    set_test_name("add test");
#endif    
    int one = 10;
    int two = 27;
    ensure_equals(one+two, ringer_client_add(one, two));
  }
  
  /**
   * Checks put
   */
  template<>
  template<>
  void tg::object::test<3>()
  {
#ifdef __APPLE__  
    set_test_name("put test");
#endif
    RNode rn;
    rn.path = test_path; 
    rn.fuse_path = test_path;
    rn.parent_path = ""; 
    rn.writer = NO_USER;
    rn.keywords.insert(test_tag);
    rn.mds_owner.host_name = ringer::RINGER_DEFUALT_MDS_PARENT_HOST;
    
    try{
      ringer_client_put(rn);
    }
    catch(InvalidOperation &io){
      ensure(false);
    }
  }
  
  /**
   * Checks get 
   */
  template<>
  template<>
  void tg::object::test<4>()
  {
#ifdef __APPLE__
    set_test_name("get test");
#endif
    string full_prefix;
    host_t host_info;
    
    GetIpAddress(&host_info);
    
    full_prefix = host_info.host_name + RINGER_HOST_ROOT_SEP;
    
    RNode rn;    
    try{
      ringer_client_get(test_path.c_str(), rn);
      ensure_equals(rn.path, full_prefix + test_path);
    }
    catch(InvalidOperation &io){
      ensure(false);
    }
  }
  
  /**
   * Checks get write permits
   */
  template<>
  template<>
  void tg::object::test<5>()
  {
#ifdef __APPLE__
    set_test_name("get write test");
#endif    
    try{
      ensure_equals(true, ringer_client_get_write(test_path.c_str()));
      // ensure_equals(true, ringer_client_get_write(test_path.c_str()));
      // ensure_equals(false, ringer_client_get_write(test_path, ui2));
    }
    catch(InvalidOperation &io){
      ensure(false);
    }
  }  
  
  
  /**
   * Checks end wite permit
   */
  template<>
  template<>
  void tg::object::test<6>()
  {
#ifdef __APPLE__
    set_test_name("end write test");
#endif    
    try{
      ringer_client_end_write(test_path.c_str());
    }
    catch(InvalidOperation &io){
      ensure(false);
    }
  }    
  
  /**
   * Checks get read permits
   */
  template<>
  template<>
  void tg::object::test<7>()
  {
#ifdef __APPLE__
    set_test_name("get read test");
#endif
    try{
      ensure_equals(true, ringer_client_get_read(test_path.c_str()));
    }
    catch(InvalidOperation &io){
      ensure(false);
    }
  }   
  
  /**
   * Checks end read permit
   */
  template<>
  template<>
  void tg::object::test<8>()
  {
#ifdef __APPLE__
    set_test_name("end read test");
#endif
    try{
      ringer_client_end_read(test_path.c_str());
    }
    catch(InvalidOperation &io){
      ensure(false);
    }
  }   
  
  /**
   * Checks client ping
   */
  template<>
  template<>
  void tg::object::test<9>()
  {
#ifdef __APPLE__
    set_test_name("client ping test");
#endif
    ensure_equals(true, ping_rnode_owner(test_path.c_str()));
  } 
  
  /**
   * Checks read and write to disk functions
   */
  template<>
  template<>
  void tg::object::test<10>()
  {
#ifdef __APPLE__
    set_test_name("Read/Write block to disk test");
#endif
    int size = ringer::g_ringer_constants.RINGER_BLOCK_SIZE;
    int offset = 0;
    
    // First, set the content.
    ringer::RingerBlock ret_val;
    
    ret_val.block_size = ringer::g_ringer_constants.RINGER_BLOCK_SIZE;
    ret_val.block_value = test_content;
    
    // And, localize the path.
    string lpath; 
    LocalizeRingerPath(test_path.c_str(), lpath);
    
    // Write content we expect to disk.
    WriteReturnValueToDisk(lpath, ret_val, offset);
    
    // Now, try reading the block back again
    ret_val.block_size = 0;
    ret_val.block_value = "";
    ReadLocalBlock(ret_val, lpath, offset, size);
    
    ensure_equals(ret_val.block_value, test_content);
  } 
  
  /**
   * Checks get_block
   */
  template<>
  template<>
  void tg::object::test<11>()
  {
#ifdef __APPLE__
    set_test_name("client get block test");
#endif
    int size = ringer::g_ringer_constants.RINGER_BLOCK_SIZE;
    int offset = 0;
    
    // First, set the content we expect.
    ringer::RingerBlock ret_val;
    ret_val.block_size = size;
    ret_val.block_value = test_content;
    
    //Now, localize the path.
    string lpath; 
    LocalizeRingerPath(test_path.c_str(), lpath);
    
    // Then write the data we expect to get.
    WriteReturnValueToDisk(lpath, ret_val, offset);
    
    // read 1 chunk over the network, expecting to get a positive value here.
    int bytes_read = read_network_file(test_path.c_str(), size, offset);
    ensure(bytes_read > 0);
    
    // now, try reading the block back again
    ret_val.block_size = 0;
    ret_val.block_value = "";
    ReadLocalBlock(ret_val, lpath, offset, size);
    
    ensure_equals(ret_val.block_value, test_content);
    
  } 
  
  /**
   * Checks send block
   * @TODO -- THIS ALWAYS FAILS -- FIX THIS.
   */
  template<>
  template<>
  void tg::object::test<12>()
  {
#ifdef __APPLE__
    set_test_name("client send block test");
#endif
    int size = ringer::g_ringer_constants.RINGER_BLOCK_SIZE;
    int offset = 0;
    
    // First, set the content we expect.
    ringer::RingerBlock ret_val;
    ret_val.block_size = size;
    ret_val.block_value = test_content;
    
    //Now, localize the path.
    string lpath; 
    LocalizeRingerPath(test_path.c_str(), lpath);
    
    // Then write the data we expect to get.
    WriteReturnValueToDisk(lpath, ret_val, offset);
    ret_val.block_size = 0;
    ret_val.block_value = "";
    ReadLocalBlock(ret_val, lpath, offset, size);
    ensure_equals(ret_val.block_value, test_content);
    
    // read 1 chunk over the network, expecting to get a positive value here.
    ensure(initiate_send_block(test_path.c_str(), size, offset));
    
    // sleep for 1 sec, to allow the system to do it's thing
    sleep(1);
    
    // now, try reading the block back again
    ret_val.block_size = 0;
    ret_val.block_value = "";
    ReadLocalBlock(ret_val, lpath, offset, size);
    
    ensure_equals(ret_val.block_value, test_content);
  }
  
  /**
  * Checks search
  */
  template<>
  template<>
  void tg::object::test<13>()
  {
    RingerSearch rs_new;    
    rs_new.search_terms.insert(test_tag);
    set<rpath_t> results;  
    try{
      ensure(ringer_client_search(results, rs_new) >= 0);
    }
    catch(TApplicationException &io){
      ensure(false);
    }
  }
  /**
   * Checks remove
   */
  template<>
  template<>
  void tg::object::test<14>()
  {
#ifdef __APPLE__
    set_test_name("remove test");
#endif
    try{
      ringer_client_remove(test_path.c_str());
    }
    catch(InvalidOperation &io){
      ensure(false);
    }
  }   
  
  void handle_timeing(const std::string& message, const struct timeval* tp_start, 
      const struct timeval* tp_end){
    std::stringstream ss;
    float time_elapsed;
     
    ss << tp_end->tv_sec - tp_start->tv_sec << "." << tp_end->tv_usec - tp_start->tv_usec;
    ss >> time_elapsed;
    time_elapsed = time_elapsed / (float)NUM_TIMES_TO_TIME;
    cout << message << " " << time_elapsed << std::endl;
  }
  
  /**
   * Checks timeing
   */
  template<>
  template<>
  void tg::object::test<15>()
  {
#ifdef __APPLE__
    set_test_name("timing test");
#endif
    // create a file, rnode
    RNode rn;
    int i;
    struct timeval tp_start;
    struct timeval tp_end;
    struct timezone tzp;
    
    string path_not_found = "/I/should/not/exist";
    rn.path = test_path; 
    rn.fuse_path = test_path;
    rn.parent_path = ""; 
    rn.writer = NO_USER;
    rn.keywords.insert(test_tag);
    rn.mds_owner.host_name = ringer::RINGER_DEFUALT_MDS_PARENT_HOST;
    
    // put rnode
    gettimeofday(&tp_start, &tzp);
    for(i=0; i<NUM_TIMES_TO_TIME; i++){
      ringer_client_put(rn);
    }
    gettimeofday(&tp_end, &tzp);    
    handle_timeing("time put", &tp_start, &tp_end);
    
    // get rnode
    gettimeofday(&tp_start, &tzp);
    for(i=0; i<NUM_TIMES_TO_TIME; i++){
        ringer_client_get(test_path.c_str(), rn);
    }
    gettimeofday(&tp_end, &tzp);
    handle_timeing("time get", &tp_start, &tp_end);   
     
    // get read
    gettimeofday(&tp_start, &tzp);
    for(i=0; i<NUM_TIMES_TO_TIME; i++){
      ringer_client_get_read(test_path.c_str());
    }
    gettimeofday(&tp_end, &tzp); 
    cout << tp_end.tv_sec - tp_start.tv_sec << "." << tp_end.tv_usec - tp_start.tv_usec << endl;
    handle_timeing("time read start", &tp_start, &tp_end);
    
    // end read
    gettimeofday(&tp_start, &tzp);
    for(i=0; i<NUM_TIMES_TO_TIME; i++){
      ringer_client_end_read(test_path.c_str());
    }
    gettimeofday(&tp_end, &tzp);
    handle_timeing("time read end", &tp_start, &tp_end);
    
    // get write
    gettimeofday(&tp_start, &tzp);
    for(i=0; i<NUM_TIMES_TO_TIME; i++){
      ringer_client_get_write(test_path.c_str());
    }
    gettimeofday(&tp_end, &tzp);
    handle_timeing("time write start", &tp_start, &tp_end);
    
    // end write
    gettimeofday(&tp_start, &tzp);
    for(i=0; i<NUM_TIMES_TO_TIME; i++){
      ringer_client_end_write(test_path.c_str());
    }
    gettimeofday(&tp_end, &tzp);
    handle_timeing("time write end", &tp_start, &tp_end);      
   }   
}

/**
 Run the test suit on the MDS
 //  tut::runner.get().register_group("MDS Tests", &tut::mds_data);
 */
int main(int argc, char** argv) {
  
  RingerNetworkArgs *net_args = new RingerNetworkArgs();
  net_args->mds_port = RINGER_DEFUALT_MDS_PORT;
  net_args->self_port = RINGER_DEFUALT_SELF_PORT;
  net_args->mds_host = RINGER_DEFAULT_MDS_HOST;
  net_args->self_host = RINGER_DEFAULT_SELF_HOST;
  net_args->local_cache_dir = RINGER_L_CACHE;
  int fuse_argc = argc;
  char **fuse_argv = argv;
  fuse_argc = parse_ringer_args(argc, argv, net_args, fuse_argv);
  
  // set this value
  SetRingerLCache(net_args->local_cache_dir);
  SetRingeDirCreateMode(0755);
  
  cout << net_args->trust_file << endl;
  
  ringer_init_network(net_args); 
  
  //now, try setting up the logger
  // Set up a simple configuration that logs on the console.
	try{
		PropertyConfigurator::configure(RINGER_LOGGING_CONFIG);		
	}
	catch(log4cxx::helpers::Exception &e){
		cerr << "Error: Could not set up the logger: " << endl;
	}
  
  tut::reporter reporter;
  tut::runner.get().set_callback(&reporter);
  tut::runner.get().run_tests();
  
  ringer_close_network();  
  
  return !reporter.all_ok();
}
